//set useful namespaces
$N("Archetype.Components",$H());


/**
 * @class Archetype.Component
 * Class for loading components
 */
$C = Archetype.Component = Archetype.Class.singleton(
/**
 * @scope Archetype.Component.prototype
 */
{
	 /**
     * Setup Component facilities constructor
     */
    initialize: _,
    /**
    * Creates a component definition
    * @param {basic HashMap} definition
    */
   create: function(/*basic HashMap*/ definition) {
               
       Archetype.ComponentDefinitions.set($U(definition.name),
       $H(Archetype.ComponentDefinitions.get($U("Archetype.component.abstractComponent"))).merge(definition).toObject());
       Archetype.ComponentManager.loadComponent($U(definition.name));
   },
   /**
    * Creates a component definition by extending another one(s)
    * @param {Array of String} parentsNames ex:["path.to.component1","path.to.component2"] latest override others in case of conflict
    * @param {HashMap} definition
    */
   Extend: function(/*Array of String*/parentsNames, /*HashMap*/definition) {
       var names = parentsNames.collect(function(o) {return $U(o);});
       var joiner = new Archetype.LoadJoiner("Component", names, this._onParentsLoaded.bind(this, parentsNames, definition));

       var needToLoad = $A();

       names.each(function(name) {
           if(typeof Archetype.Components.get(name) == "function") {
               joiner.getJoiner(name)();
           } else {
               needToLoad.push($U(name));
           }
       });

       Archetype.require(needToLoad, {
           evaluation: "component"
       });
   },
   /**
    * get a Archetype.Component by it's UMN
    * @param {UMN or String} umn object or realPath
    * @return {Archetype.Component}
    */
   get: function(/*UMN*/ umn) {
       return Archetype.Components.get($U(umn));
   },
   /**
    * Build the extended component when Parents are loaded
    * @param {Array of String} parentsNames ex:["path.to.component1","path.to.component2"] latest override others in case of conflict
    * @param {HashMap} definition
    * @private
    */
   _onParentsLoaded: function(/*Array of String*/parentsNames, /*HashMap*/definition) {
       var finalDefinition= $H(definition);

       //extend the definition with each parent definition
       $A(parentsNames).each(function(parentName){
           var parentDef = Archetype.ComponentDefinitions.get($U(parentName));
           finalDefinition = $H(parentDef).merge(finalDefinition).toObject();
       });
       Archetype.ComponentDefinitions.set($U(definition.name), finalDefinition);
       Archetype.ComponentManager.loadComponent/*DefinitionFromPath*/($U(definition.name));
   }

}).instance();

/**
 * @class
 * Archetype.Component manager. Sets up components, load their dependencies, etc.
 * Should only be used through the convenient "Archetype.Component." wrappers
 */
Archetype.ComponentManager = Archetype.Class.singleton(
/** @scope Archetype.ComponentManager.prototype */
{
    /**
     * Setup Archetype.Component Management configuration
     */
    initialize: function() {
        Archetype.require("Archetype.component.abstractComponent", {asynchronous: false});
        this.methodManager= new MethodManager();
    },
    /**
     * Initialize the component definition.
     * Launch the loading of dependencies.
     * @param {String} module
     */
    loadComponent: function(/*String or UniqueModuleName*/ module) {
        var umn = $U(module);

        var componentDefinition = Archetype.ComponentDefinitions.get(umn);
        componentDefinition.setup.umn = umn;

        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] loadComponent start ", componentDefinition.name);
        }
        
        var setupJoiner = new Archetype.Joiner(["components", "templates", "libs"], this.startComponent.bind(this, componentDefinition));

		Archetype.require($H(componentDefinition.setup.dependencies.components).values(), {
            evaluation: "component",
            callback: this.componentsDependenciesLoaded.bind(this, componentDefinition, setupJoiner)
        });

        //Template system
        if(typeof componentDefinition.setup.dependencies.html != "undefined") {
            if(typeof componentDefinition.setup.template == "undefined") {
            	var wrapper = "Archetype.template." + Archetype.template + "Template";
            	componentDefinition.setup.dependencies.lib.push(wrapper);
            } else {
                if(componentDefinition.setup.template.indexOf("template.") === 0) {
                    //Hack because modules conf doesn't work fine for this need
                    componentDefinition.setup.template = "Archetype." + componentDefinition.setup.template;
                }
                var wrapper = "Archetype.template." + componentDefinition.setup.template + "Template";
                componentDefinition.setup.dependencies.lib.push(componentDefinition.setup.template);
            }
        }

        //Libraries
        Archetype.require(componentDefinition.setup.dependencies.lib, {
            evaluation: "js",
            asynchronous: true,
            callback: this.librariesDependenciesLoaded.bind(this, componentDefinition, setupJoiner)
        });

        //Styles
        if(typeof componentDefinition.setup.dependencies.css != "undefined") {
            Archetype.require(componentDefinition.setup.dependencies.css, {
                evaluation: "css",
                asynchronous: true
            });
        }
    },
    /**
     * Called when all component's components dependancies are available.
     * Add a HashMap with components constructors in the options of the joiner in order to adding them in the new component definition.
     * @param {HashMap} componentDefinition
     * @param {Archetype.Joiner} setupJoiner
     */
    componentsDependenciesLoaded: function(/*String []*/ componentDefinition, /*Archetype.Joiner*/ setupJoiner) {
    	var components = $H();

        $H(componentDefinition.setup.dependencies.components).each(function(pair) {
            components.set(pair.key, Archetype.Components.get($U(pair.value)));
        });

        setupJoiner.getJoiner("components", {components: components.toObject()})();
        
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] componentsDependenciesLoaded ", componentDefinition.name);
        }
    },
    /**
     * Called when all libraries are available.
     * We have to test if there is a specialized template system
     * @param {HashMap} componentDefinition
     * @param {Archetype.Joiner} setupJoiner
     */
    librariesDependenciesLoaded: function(/*String []*/ componentDefinition, /*Archetype.Joiner*/ setupJoiner) {
        setupJoiner.getJoiner("libs")();
		
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] librariesDependenciesLoaded ", componentDefinition.name);
        }
        
        if(typeof componentDefinition.setup.dependencies.html != "undefined") {
            if(typeof componentDefinition.setup.template == "undefined") {
                componentDefinition.setup.templateSystem = Archetype.Template;
            } else {
                var templateName = componentDefinition.setup.template.replace(/^(Archetype\.)?template\./,"");
                templateName = templateName.substr(0, 1).toUpperCase() + templateName.substr(1);
                componentDefinition.setup.templateSystem = Archetype.Templates[templateName];
            }
        }
		
        componentDefinition.templates = {};
        if (typeof componentDefinition.setup.dependencies.html == "undefined") {
            setupJoiner.getJoiner("templates")();
        } else {
            Archetype.require($H(componentDefinition.setup.dependencies.html).values(), {
                evaluation: "html",
                callback: this.templatesDependenciesLoaded.bind(this, componentDefinition, setupJoiner),
                storing: componentDefinition.templates
            });
        }
    },
    /**
     * Called when all component's templates dependancies are available.
     * Add a HashMap with templates in the options of the joiner in order to adding them in the new component definition.
     * @param {HashMap} componentDefinition
     * @param {Archetype.Joiner} setupJoiner
     */
    templatesDependenciesLoaded: function(/*String []*/ componentDefinition, /*Archetype.Joiner*/ setupJoiner) {
        var keys = $H(componentDefinition.setup.dependencies.html).keys();
        var paths = $H(componentDefinition.setup.dependencies.html).values();
		
        var templates = $H(componentDefinition.templates);
		
        for(var i = 0; i < paths.length; i++) {
            var key = $U(paths[i], "html");
            templates.set(keys[i], new componentDefinition.setup.templateSystem(templates.get(key)));
        }
        setupJoiner.getJoiner("templates", {templates: templates.toObject()})();
        
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] templatesDependenciesLoaded ", componentDefinition.name);
        }
    },
    /**
     * Generate a component constructor from definition
     * @param {Object} componentDefinition
     */
    prototypizeComponent: function(_componentDefinition) {
        //Logger.debug("Creating a component");
        var _constructor= function() {
            componentDefinition = $H(_componentDefinition);
            var _private = this._private = {
                _definition: _componentDefinition,
                components: _componentDefinition.components,
                templates: _componentDefinition.templates
            }

            //should correctly clone the  componentDefiniton Prototype Hashmap
            this._private=componentDefinition.merge(_private).toObject();
                                
            Archetype.ComponentManager.methodManager.buildMethods(this);

            if (componentDefinition.get("initialize")) {
                componentDefinition.get("initialize").apply(this._private, arguments);
            }
        };

        var definition= {
            initialize: _constructor
        };

        return Archetype.Class.create(definition);
    },
    /**
     * Called when all component's dependencies are available
     * Create the component and return the corresponding object
     * Run new component instance
     * @param {HashMap} componentDefinition
     * @param {HashMap} options options from the joiner, contain hashmaps for components and templates dependencies
     */
    startComponent: function(/*HashMap*/ componentDefinition, /*HashMap*/ options) {
        componentDefinition.components = options.get("components");
        componentDefinition.templates = options.get("templates");

        var umn = componentDefinition.setup.umn;
        
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] before prototypize ", componentDefinition.name);
        }

        Archetype.Components.set(umn, this.prototypizeComponent(componentDefinition));

        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] after prototypize ", componentDefinition.name);
        }
        
        //Archetype.dispatchEvent("Component", umn);
        
        Logger.debug("Component loaded : "+ umn);
        Archetype.engine.fire("Component", umn);
    }
}).instance();